package tf_rna_hit;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
 * 
 * @author zhang
 *	2016.09.24	最后一版，完成
 */

public class Get_hits {
	public static void main(String args[]){
		if(args.length == 0|| args[0].equals("-h") || args[0].equals("--help")){
			System.out.println("完全重铸这个程序");
			System.out.println("还在苦死到底怎么办");
			System.out.println("不瞎搞那么多了");
			System.out.println("第一个文件，lnc的bed格式文件，第五列修改为tss位点信息");
			System.out.println("第二个参数为tf的bed格式文件");
			System.out.println("-p int	启用多线程");
			System.out.println("-s int	更换上游界限");
			System.out.println("-x int 更换下游界限");
			return;
		}
		long begainTime = System.currentTimeMillis();
		
		try{
			Map<String, List<Bedlnc>> lnc = new HashMap<String, List<Bedlnc>>();	//	全部改用map类来分染色体储存相应的信息
			Map<String, List<Bed>> tf = new HashMap<String, List<Bed>>();	//	用来收集tf位点信息的list类
			String content = null;
			List<RnaTf> lnctf = new ArrayList<RnaTf>();	//	新建的List类，用来存储lnc和tf的匹配信息		
			Map<String, Integer> fin = new HashMap<String, Integer>();	//	新建的特殊Map类，用来存储最终统计的tfbs数目结果
			int shang = -1, xia = -1, thread = 1;
			int order = -1;
			
			
			// 读取文件还是必须的
			File file0 = new File(args[0]);
			
			if(file0.exists() && file0.isFile()){
				System.out.println("开始处理" + args[0]);                                                                                         
			}else{
				System.out.println("未发现文件" + args[0]);
				return;
			}
			
			//	遍历参数提取相应的信息
			for(int i = 0; i < (args.length - 1); i++){
				if(args[i].equals("-s")){
					shang = Integer.parseInt(args[i+1]);
				}else if(args[i].equals("-x")){
					xia = Integer.parseInt(args[i+1]);
				}else if(args[i].equals("-p")){
					thread = Integer.parseInt(args[i+1]);
				}
			}
			// 第一个文件特殊处理吧
			FileReader fr0 = new FileReader(file0);
			BufferedReader br0 = new BufferedReader(fr0);
			
			while((content = br0.readLine()) != null){
				String[] contentarray = content.split("\\s+");
				//	首先判断是否map里是否有这个key，根据有无相应新建临时的list文件，一遍map添加
				//	其次根据参数的长度判断是否需要单独设定block的大小
				if(lnc.containsKey(contentarray[0])){
					List<Bedlnc> tem = lnc.get(contentarray[0]);
					
					if(order > Integer.parseInt(contentarray[1]) ){
						System.out.println(args[0] + "没有经过排序");
						System.out.println("e.g., sort -k1,1 -k2,2n in.bed > in.sorted.bed for BED files");
						return;
					}else{
						order =  Integer.parseInt(contentarray[1]);
					}
					
					if(shang > 0 && xia > 0){
						Bedlnc tembed = new Bedlnc(contentarray[0], Integer.parseInt(contentarray[1]),
								Integer.parseInt(contentarray[2]), contentarray[3], 
								Integer.parseInt(contentarray[1]), contentarray[5], shang,  xia);			
						
						tem.add(tembed);	
						
						System.out.println(tembed.getBlockend() - tembed.getBlockstart());
					}	else{
						Bedlnc tembed = new Bedlnc(contentarray[0], Integer.parseInt(contentarray[1]),
								Integer.parseInt(contentarray[2]), contentarray[3], 
								Integer.parseInt(contentarray[1]), contentarray[5]);
						
						tem.add(tembed);
					}
					
					lnc.put(contentarray[0], tem);
				}else{
					List<Bedlnc> tem = new ArrayList<Bedlnc>();
					order =  Integer.parseInt(contentarray[1]);
						
					if(shang > 0 && xia > 0){
						Bedlnc tembed = new Bedlnc(contentarray[0], Integer.parseInt(contentarray[1]),
								Integer.parseInt(contentarray[2]), contentarray[3], 
								Integer.parseInt(contentarray[1]), contentarray[5], shang,  xia);			
						
						tem.add(tembed);	
						
						System.out.println(tembed.getBlockend() - tembed.getBlockstart());
					}	else{
						Bedlnc tembed = new Bedlnc(contentarray[0], Integer.parseInt(contentarray[1]),
								Integer.parseInt(contentarray[2]), contentarray[3], 
								Integer.parseInt(contentarray[1]), contentarray[5]);
						
						tem.add(tembed);
					}
									
					lnc.put(contentarray[0], tem);
				}
			}
			
			
			br0.close();
			fr0.close();
			
			System.out.println(args[0] + "读取完成");
			
			// 开始读取第二份文件
			
			File file1 = new File(args[1]);
			
			if(file1.exists() && file1.isFile()){
				System.out.println("开始处理" + args[1]);
			}else{
				System.out.println("未发现文件" + args[1]);
				return;
			}
			
			
			
			FileReader fr1 = new FileReader(file1);
			BufferedReader br1 = new BufferedReader(fr1);
			
			while((content = br1.readLine()) != null){
				String[] contentarray = content.split("\\s+");
				
				if(tf.containsKey(contentarray[0])){
					List<Bed> tem = tf.get(contentarray[0]);
					
					if(order > Integer.parseInt(contentarray[1])){
						System.out.println(args[1] + "没有经过排序");
						System.out.println("e.g., sort -k1,1 -k2,2n in.bed > in.sorted.bed for BED files");
						return;
					}else{
						order =  Integer.parseInt(contentarray[1]);
					}
							
					tem.add(new Bed(contentarray[0], 
						Integer.parseInt(contentarray[1]),
						Integer.parseInt(contentarray[2]),
						contentarray[3]));
					
					tf.put(contentarray[0], tem);
				}else{
					List<Bed> tem = new ArrayList<Bed>();
					
					order = Integer.parseInt(contentarray[1]);
					
					tem.add(new Bed(contentarray[0], 
							Integer.parseInt(contentarray[1]),
							Integer.parseInt(contentarray[2]),
							contentarray[3]));
						
						tf.put(contentarray[0], tem);
				}	
			}
			
			br1.close();
			fr1.close();
			
			System.out.println(args[1] + "读取完成");
			
			//	正式开始多线程计算
			if(thread > 1){
						
				int groupSize = lnc.size()/thread;	//	根据线程数确定需要分割成每个线程计算多少
				Object[] key = lnc.keySet().toArray();
				
				List<Map<String, List<Bedlnc>>> runlnc = new ArrayList<Map<String, List<Bedlnc>>>(thread);	//	用来收集分开的lnc的map类
				List<Thread> threads = new ArrayList<Thread>(thread); 	//	新建thread的list类
			
				
				
				for(int i = 0; i <thread; i++){
					Map<String, List<Bedlnc>> temmap = new HashMap<String, List<Bedlnc>>();
					if(i < thread - 1){
						for(int j = groupSize * i;  j < groupSize*(i+1); j++){
							temmap.put(key[j].toString(), lnc.get(key[j].toString()));
						}
					}else{
						for(int j = groupSize * i;  j < lnc.size();  j++){
							temmap.put(key[j].toString(), lnc.get(key[j].toString()));
						}
					}
					runlnc.add(temmap);				
				}
				
				//	新建thread
				for(int i = 0; i < thread; i++){
					Multi temMulti = new Multi(runlnc.get(i), tf, "线程" + (i  + 1) + "：" , args[2] + ".tem" + i);					
					threads.add(new Thread(temMulti));
				}
				//	开始分线程运算
				for(int i = 0; i < thread; i ++){
					threads.get(i).start();
				}
			
				Thread.sleep(4000L);						//	主线程沉睡
				
				//	开始join
				for(int i = 0; i < thread; i++){
					threads.get(i).join();
				}
				
				//	将建立的临时文件在读回来
				for(int i = 0; i < thread; i++){
					File tem = new File(args[2] + ".tem" + i);
					
					if(tem.exists() & tem.isFile()){
						System.out.println("读取临时文件：" + tem);
					}else{
						System.out.println("临时文件" + tem + "不存在");
						return;
					}
					
					FileReader fr = new FileReader(tem);
					BufferedReader br = new BufferedReader(fr);
					
					while((content = br.readLine()) != null){
											
						if(fin.containsKey(content)){
							fin.put(content, fin.get(content) + 1);
						}else{
							fin.put(content, 1);
						}					
					}
					
					// 临时文件无用，删除
					tem.delete();
				}		
			}else{	//	正式开始单线程计算
				
				
				lnctf = get_hits(lnc, tf, "");
				
				//	计算完成，清除没用的list，节省内存
				tf.clear();
				lnc.clear();				
				
				//	开始统计每个lnc和tf匹配的数目
				fin = count_num(lnctf);
			}
			
			// 多线程统计结果的输出
			File output = new File(args[2]);
			// 判断文件是否存在
			if(output.exists() && output.isFile()){
				System.out.println("使用已经存在的文件" + output);
			}else{
				try{
					output.createNewFile();
					System.out.println("创建新文件");
				}catch (IOException e){
					System.out.println("创建文件失败，错误信息：" + e.getMessage());
					return;
				}
			}
			
			FileWriter fw = new FileWriter(output);
			BufferedWriter bw = new BufferedWriter(fw);
			
			if(fin.size() == 0){
				System.out.println("没有匹配，程序终止");
				return;
			}else{
				bw.write(new RnaTf().printAllheader());
				bw.newLine();
			}
			
			Iterator<String> iter = fin.keySet().iterator();	//	Iterator遍历整个Map
			
			while(iter.hasNext()){
				String keyStr = iter.next();
				
				bw.write(keyStr + "\t" + fin.get(keyStr));
				bw.newLine();
			}
					
			bw.close();
			fw.close();
			
		}catch(FileNotFoundException e){
			e.printStackTrace();
		}catch(IOException e){
			e.printStackTrace();
		} catch (InterruptedException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
		
		System.out.println("程序运行结束");
		long endTime = System.currentTimeMillis();
		Time time = new Time();
		time.run_time(begainTime, endTime);
	}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////	
	private static List<RnaTf> get_hits(Map<String, List<Bedlnc>> lnc, Map<String, List<Bed>> tf, String message){
	
		List<RnaTf> results = new ArrayList<RnaTf>();	//	用来保存输出结果
				
		//	然后首先就是提取相应染色体的lnc和tf出来减少不必要的循环

		
		for(String key : lnc.keySet()){
		
			List<Bedlnc> temlnc = lnc.get(key);

			if(!tf.containsKey(key)){continue;}	//	如果tf没有相应染色体的数据，就直接略过
			List<Bed> temtf = tf.get(key);			
			results.addAll(real_hits(temlnc, temtf, message +  "计算" + key + "中："));			
		}

		return results;		
	}
	/************************************************************************************************************/
	//	真正用来统计hits的函数
	private static List<RnaTf> real_hits(List<Bedlnc> lnc, List<Bed> tf, String key){
		List<RnaTf> results = new ArrayList<RnaTf>();
		Pattern pattern = Pattern.compile("[+,-]");
		int jindu = 0, count = 0;
		
		for(int i = 0; i < lnc.size(); i++){
			Matcher machlnc = pattern.matcher(lnc.get(i).getStrand());
			for(int j = 0; j < tf.size(); j++){
				Matcher machtf = pattern.matcher(tf.get(j).getStrand());	
				//	通过正则来判断两中序列是否分正负链
				if(machlnc.matches() && machtf.matches()){
					if(lnc.get(i).getStrand() == tf.get(j).getStrand()){
						if(lnc.get(i).getBlockstart() <= tf.get(j).getEnd() && lnc.get(i).getBlockend() >= tf.get(j).getStart() ){
							results.add(new RnaTf(lnc.get(i).getName(), tf.get(j).getName(), lnc.get(i).getChr(),
									lnc.get(i).getStart(), lnc.get(i).getEnd(), lnc.get(i).getStrand(),
									lnc.get(i).getTss()));
						}else if(lnc.get(i).getBlockstart() > tf.get(j).getEnd()){
							continue;	//	因为原始文件经过排序，所以如果范围超出了，后边的就没必要继续计算了
						}
					}
				}else{
					if(lnc.get(i).getBlockstart() <= tf.get(j).getEnd() && lnc.get(i).getBlockend() >= tf.get(j).getStart() ){
						results.add(new RnaTf(lnc.get(i).getName(), tf.get(j).getName(), lnc.get(i).getChr(),
								lnc.get(i).getStart(), lnc.get(i).getEnd(), lnc.get(i).getStrand(),
								lnc.get(i).getTss()));
					}else if(lnc.get(i).getBlockstart() > tf.get(j).getEnd()){
						continue;	//	因为原始文件经过排序，所以如果范围超出了，后边的就没必要继续计算了
					}
				}
			}
			count++;
			if(jindu != (count * 100 / lnc.size())){
				jindu = count * 100 / lnc.size();
				System.out.println(key + jindu + "%");
			}
		}	
		return results;
	}
	
	
	/*******************************************************************************************************************/
	// 用来统计所有的tfbs的数量的，数目少的时候可以用一用，但是需要调用多线程的时候数据量会很大，用它不方便
	private static Map<String, Integer> count_num(List<RnaTf> input){
		int jindu = 0;	
		
		Map<String, Integer> results = new HashMap<String, Integer>();	// 用来存储结果的map类，int为重复的次数，numofhit.
		
		for(int i = 0; i < input.size(); i++){
			if(results.containsKey(input.get(i).printTem())){
				results.put(input.get(i).printTem(), results.get(input.get(i).printTem()) + 1);
			}else{
				results.put(input.get(i).printTem(), 1);
			}
			
			if(jindu != (i * 100)/input.size()){
				jindu = (i * 100)/input.size();
				System.out.println("count_num的进度：" + jindu + "%");
			}
			
		}
		return results;
	}


	/*******************************************************************************************************/
	private static class Multi implements Runnable{
		private Map<String, List<Bedlnc>> lnc;
		private Map<String, List<Bed>> tf;
		private String num;
		private String temfile;
		//	构造方法
		public Multi(Map<String, List<Bedlnc>> lnc, Map<String, List<Bed>> tf, String num, String temfile){
			this.lnc = lnc;
			this.tf = tf;
			this.num = num;
			this.temfile = temfile;
		}
		
		public void run(){
			
			System.out.println(num + "开始工作");
			
			List<RnaTf> results =get_hits(lnc, tf,  num );

			File tem = new File(temfile);
			// 检车临时文件是否存在
			if(tem.exists() & tem.isFile()){
				System.out.println("覆盖临时文件" + tem);
			}else{
				try{
					tem.createNewFile();
					System.out.println("创建临时文件" + tem);
				}catch (IOException e){
					System.out.println("创建文件失败，错误信息：" + e.getMessage());
					return;
				}
			}
			// 将该线程的数据输出
			FileWriter fw;
			try {
				fw = new FileWriter(tem);
				BufferedWriter bw = new BufferedWriter(fw);
				
				for(int i = 0; i < results.size(); i++){
					bw.write(results.get(i).printTem());
					bw.newLine();
				}
				
				bw.close();
				fw.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
}
